/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.web.misc.cg;

import com.google.common.base.Function;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import edu.emory.mathcs.backport.java.util.Collections;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.vacoor.nothing.common.util.Resources;
import org.vacoor.nothing.common.util.StringUtils;
import org.vacoor.nothing.common.util.Throwables;
import org.vacoor.nothing.web.dao.support.mbt.MyBatis;
import org.vacoor.nothing.web.misc.cg.descriptor.MappedColumn;
import org.vacoor.nothing.web.misc.cg.descriptor.MappedTable;
import org.vacoor.nothing.web.misc.cg.descriptor.OneToOne;
import org.vacoor.nothing.web.misc.cg.extractor.impl.JavaMappingDescriptorExtractor;
import org.vacoor.nothing.web.misc.codegen.Jdbcs;
import org.vacoor.nothing.web.misc.codegen.SimpleMapper;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.*;
import java.io.*;
//import java.nio.file.FileAlreadyExistsException;
import java.sql.*;
import java.util.*;

/**
 * @author vacoor
 */
public class Cgs {
    // private static final String TPL_DIR = "/support/tpl";
    private static final String TPL_DIR = "/tpl4glanway";
    public static final int COMP_ENTITY = 1;
    public static final int COMP_DAO = 1 << 1;
    public static final int COMP_MBT_MAPPER = 1 << 2;
    public static final int COMP_HBN_DAO_IMPL = 1 << 3;
    public static final int COMP_SERVICE = 1 << 4;
    public static final int COMP_SERVICE_IMPL = 1 << 5;
    public static final int COMP_DEFAULT = COMP_DAO | COMP_MBT_MAPPER | COMP_SERVICE | COMP_SERVICE_IMPL;

    public static void main(String[] args) throws IOException, SQLException, TemplateException, XPathExpressionException, ParserConfigurationException, SAXException {
        generateCrudCode(new File[]{
                        new File("E:\\gx\\WorkerInformation\\WorkerInformation"),
                        new File("C:\\Users\\Administrator\\IntelliJProjects\\gone"),
                        new File("C:\\Users\\Administrator\\IntelliJProjects\\nothing\\nothing-web")
                },
                new File("D:\\gx\\WorkerInformation\\WorkerInformation\\src\\main\\java"),
                new File("D:\\gx\\WorkerInformation\\WorkerInformation\\src\\main\\resources"),
//                COMP_DEFAULT
                COMP_MBT_MAPPER
//                COMP_DAO | COMP_SERVICE_IMPL | COMP_SERVICE
        );
        /*
        final String url = "jdbc:oracle:thin:@192.168.1.123:1521:ORCL";
        final String username = "jiusu";
        final String password = "jiusu";
        final String schema = username.toUpperCase();
        final EntityMapper mapper = new SimpleMapper();


        Connection conn = DriverManager.getConnection(url, username, password);
        load(conn, username.toUpperCase());
        */
    }

    static Map<String/*table*/, MappedTable> load(Connection conn, String schema) throws SQLException, IOException, TemplateException {
        Configuration config = new Configuration();
        config.setClassForTemplateLoading(Cgs.class, TPL_DIR);
        Template tpl = config.getTemplate("Entity.ftl");

        Map<String, MappedTable> tableMap = Maps.newHashMap();
        DatabaseMetaData metaData = conn.getMetaData();
        List<Jdbcs.DbTable> dbTables = Jdbcs.readTables(metaData, schema, null, null);
        Map<String, Jdbcs.DbTable> dbTablesMap = Maps.uniqueIndex(dbTables, new Function<Jdbcs.DbTable, String>() {

            @Override
            public String apply(Jdbcs.DbTable input) {
                return input.name;
            }
        });

        for (Jdbcs.DbTable dbTable : dbTables) {
            create(tableMap, dbTablesMap, metaData, dbTable.name);
        }
        for (MappedTable t : tableMap.values()) {
            processTemplate(tpl, ImmutableMap.of("tab", t), "c:/outout/" + t.getTypeName() + ".java");
            // tpl.process(ImmutableMap.of("tab", t), new PrintWriter(System.out));
        }
        return null;
    }

    static MappedTable create(Map<String, MappedTable> cache, Map<String, Jdbcs.DbTable> dbTablesMap, DatabaseMetaData metaData, String table) throws SQLException {
        MappedTable mappedTable = cache.get(table);
        if (null != mappedTable) {
            return mappedTable;
        }
        cache.put(table, mappedTable = new MappedTable());

        mappedTable.setTable(table);
        String typeName = table.startsWith("TB_") ? table.substring(3) : table;
        typeName = StringUtils.underscoreToCamelCase(typeName, true);
        mappedTable.setTypeName(typeName);
        mappedTable.setFullTypeName("com.test.xx." + typeName);

        Jdbcs.DbTable dbTable = dbTablesMap.get(table);

        // 获取所有外键
        List<String> fks = Lists.newArrayList();
        ResultSet rs = metaData.getImportedKeys(null, dbTable.schema, dbTable.name);
        if (rs.next()) {
            String pkTableSchema = rs.getString("PKTABLE_SCHEM");
            String pkTableName = rs.getString("PKTABLE_NAME");
            String pkColumnName = rs.getString("PKCOLUMN_NAME");
            String fkTableSchema = rs.getString("FKTABLE_SCHEM");
            String fkTableName = rs.getString("FKTABLE_NAME");
            String fkColumnName = rs.getString("FKCOLUMN_NAME");
            int keySeq = rs.getInt("KEY_SEQ");

            fks.add(fkColumnName);

            MappedTable refTable = create(cache, dbTablesMap, metaData, pkTableName);
            OneToOne rel = new OneToOne(pkTableSchema + "." + pkTableName);
            String prop = fkColumnName.toUpperCase().endsWith("_ID") ? fkColumnName.substring(0, fkColumnName.length() - 3) : fkColumnName;
            prop = StringUtils.underscoreToCamelCase(prop, false);

            rel.setProperty(prop);
            rel.setJoinColumn(fkColumnName);
            rel.setRel(refTable);

            mappedTable.addOneToOne(rel);

            System.out.println(keySeq + ": " + fkTableSchema + "." + fkTableName + "(" + fkColumnName + ") REF " + pkTableSchema + "." + pkTableName + "(" + pkColumnName + ")");
            //
            System.out.println();
        }
        // 当只有两个字段除了主键和逻辑删除以及审计字段外所有的字段都是外键时候认为是不需要生成的 many to many 中间表

        // 处理所有列
        SimpleMapper mapper = new SimpleMapper();
        List<Jdbcs.DbColumn> dbColumns = Jdbcs.readColumns(metaData, dbTable.schema, dbTable.name, null);
        for (Jdbcs.DbColumn dbColumn : dbColumns) {
            if (fks.contains(dbColumn.name)) {
                continue;
            }
            String fullName = mapper.getJavaTypeFullName(dbColumn.name, dbColumn.type, dbColumn.sqlType, dbColumn.length, dbColumn.scale);
            MappedColumn col = new MappedColumn();
            col.setColumn(dbColumn.name);
            col.setProperty(StringUtils.underscoreToCamelCase(dbColumn.name, false));
            col.setJavaTypeName(fullName.substring(fullName.lastIndexOf(".") + 1));
            col.setJavaFullTypeName(fullName);
            mappedTable.addColumn(col);
        }
        return mappedTable;
    }

    /* ************************************************************
     *
     * ********************************************************** */

    public static void generateCrudCode(Reader configReader, File outPath, String basePackage) throws IOException {
        if (!outPath.exists()) outPath.mkdirs();
        File javaSourcePath = new File(outPath, "java");
        File resourcePath = new File(outPath, "resources");

        // TODO generate entity from config
        if (true) throw new UnsupportedOperationException();

        configReader.close();

        generateCrudCode(new File[]{javaSourcePath}, javaSourcePath, resourcePath, COMP_DEFAULT);
    }

    /**
     * 扫描给定的路径, 生成 CRUD 代码
     *
     * @param srcPaths          源码目录
     * @param javaSourceOutPath java文件输出路径
     * @param resourceOutPath   资源文件输出路径
     * @param comps             要生成的组件
     * @throws IOException
     */
    public static void generateCrudCode(File srcPaths[], File javaSourceOutPath, File resourceOutPath, int comps) throws IOException {
        JavaMappingDescriptorExtractor extractor = new JavaMappingDescriptorExtractor();
        List<MappedTable> descriptors = extractor.execute(srcPaths);
        generateCrudCode(descriptors, javaSourceOutPath, resourceOutPath, comps);
    }

    /**
     * 生成 CRUD 代码
     *
     * @param descriptors           映射描述符
     * @param javaSourceOutPath     java文件输出目录
     * @param resourceSourceOutPath 资源文件输出目录
     * @param comps                 要生成的组件
     * @throws IOException
     */
    public static void generateCrudCode(List<MappedTable> descriptors, File javaSourceOutPath, File resourceSourceOutPath, int comps) throws IOException {
        List<MappedTable> mappedTables = null != descriptors ? descriptors : Collections.emptyList();
        Configuration config = new Configuration();
        config.setClassForTemplateLoading(MyBatis.class, TPL_DIR);

        for (MappedTable mappedTable : mappedTables) {
            String entityName = mappedTable.getTypeName();
            String fullEntityName = mappedTable.getFullTypeName();
            String entityPackage = fullEntityName.substring(0, fullEntityName.length() - entityName.length() - 1);
            String packageName = entityPackage.substring(0, entityPackage.lastIndexOf("."));
            String daoPackage = packageName + ".dao";
            String mapperPackage = daoPackage;
            String servicePackage = packageName + ".service";
            String serviceImplPackage = packageName + ".service.impl";

            Map model = Maps.newHashMap();
            model.put("tab", mappedTable);

            model.put("package", packageName);
            model.put("entityName", entityName);
            model.put("entityPackage", entityPackage);
            model.put("daoPackage", daoPackage);
            model.put("mapperPackage", mapperPackage);
            model.put("servicePackage", servicePackage);
            model.put("serviceImplPackage", serviceImplPackage);

            String javaSourcePath = javaSourceOutPath.getAbsolutePath();
            String resourcePath = resourceSourceOutPath.getAbsolutePath();

            javaSourcePath = javaSourcePath.endsWith("/") ? javaSourcePath : javaSourcePath + "/";
            resourcePath = resourcePath.endsWith("/") ? resourcePath : resourcePath + "/";

            if (0 != (COMP_DAO & comps)) {   // dao
                processTemplate(config.getTemplate("CrudDao.ftl"), model, javaSourcePath + toPath(daoPackage) + "/" + entityName + "Dao.java");
            }
            if (0 != (COMP_MBT_MAPPER & comps)) {    // mybatis mapper
                processTemplate(config.getTemplate("CrudMapper.ftl"), model, resourcePath + toPath(mapperPackage) + "/" + entityName + "Mapper.xml");
            }
            if (0 != (COMP_SERVICE & comps)) { // service
                processTemplate(config.getTemplate("CrudService.ftl"), model, javaSourcePath + toPath(servicePackage) + "/" + entityName + "Service.java");
            }
            if (0 != (COMP_SERVICE_IMPL & comps)) { // service impl
                processTemplate(config.getTemplate("CrudServiceImpl.ftl"), model, javaSourcePath + toPath(serviceImplPackage) + "/" + entityName + "ServiceImpl.java");
            }
        }
    }

    /**
     * 处理模板
     */
    private static void processTemplate(Template tpl, Object model, String path) throws IOException {
        File file = new File(path);
        File dir = file.getParentFile();
        if (file.exists()) {
            // throw new FileAlreadyExistsException(path);
            // throw new IOException("file already exists:" + path);
            System.err.println("skip, file already exists:" + path);
            return;
        }
        if (!dir.exists()) {
            dir.mkdirs();
        }

        FileWriter fileWriter = new FileWriter(file);
        try {
            tpl.process(model, fileWriter);
            fileWriter.flush();
            fileWriter.close();
        } catch (TemplateException e) {
            Throwables.unchecked(e);
        }
    }

    /**
     * 包 -> 路径
     */
    private static String toPath(String packageName) {
        if (null == packageName) {
            return "";
        }
        return packageName.replace(".", "/");
    }

    /* ******************************************
     *
     * **************************************** */

    static void parsePDM() throws ParserConfigurationException, IOException, SAXException {
        Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(Resources.getResourceAsStream("IdianJia.pdm"));
        Element root = doc.getDocumentElement();
        // 所有 Package 下的 c:Tables
        NodeList tablesList = doc.getElementsByTagName("c:Tables");
        for (int i = 0; i < tablesList.getLength(); i++) {
            Element tables = (Element) tablesList.item(i);
            System.out.println(tables.getNodeName());

            NodeList tableList = tables.getElementsByTagName("o:Table");
            for (int j = 0; j < tableList.getLength(); j++) {
                Element table = (Element) tableList.item(j);
                String tableId = table.getAttribute("Id");      //
                String tableName = text(one(table, "a:Name"));          // 表名称
                String tableCode = text(one(table, "a:Code"));          // 表Code
                String tableComment = text(one(table, "a:Comment"));    // 表注释
                NodeList columns = one(table, "c:Columns").getElementsByTagName("o:Column");

                for (int k = 0; k < columns.getLength(); k++) {
                    Element col = (Element) columns.item(k);
                    String colId = col.getAttribute("Id");
                    String colName = text(one(col, "a:Name"));          // 列名称
                    String colCode = text(one(col, "a:Code"));          // 列Code
                    String colDataType = text(one(col, "a:DataType"));  // 列数据类型
                    String colLength = text(one(col, "a:Length"));  // 列数据类型
                    String colComment = text(one(col, "a:Comment"));    // 列注释
                    String identity = text(one(col, "a:Identity"));     // 是否标识符
                    String mandatory = text(one(col, "a:Mandatory"));    // 是否非空
                    System.out.println(colName + "(" + colCode + ")" + ", " + colDataType + ", " + identity + " " + mandatory);
                }

                System.out.println(table.getNodeName() + "," + tableName + "(" + tableCode + ")" + ", " + tableId + "," + tableComment);
            }
        }
        // 所有Package 下的 c:References
        NodeList referencesList = doc.getElementsByTagName("c:References");
        for (int i = 0; i < referencesList.getLength(); i++) {
            Element references = (Element) referencesList.item(0);

            references.getElementsByTagName("o:Reference");
        }
    }

    static String text(Element el) {
        return null != el ? el.getTextContent() : "";
    }

    static Element one(Element context, String tagName) {
        NodeList list = context.getElementsByTagName(tagName);
        return (Element) (0 < list.getLength() ? list.item(0) : null);
    }
}
